# 高级特性

# 通过循环构造列表
L1 = []
n = 1
while n < 99:
    L1.append(n)
    n += 1
print(L1)

# 切片
# 取一个list或tuple的部分元素是非常常见的操作
L2 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']
print(L2[0])
for i in range(3):
    print('for-->' + L2[i])
# 对这种经常取指定索引范围的操作，用循环十分繁琐，因此，Python提供了切片（Slice）操作符，能大大简化这种操作
print('Slice-->', L2[0:4])
print('Slice-->', L2[3:4])
print('Slice-->', L2[4:4])
print('Slice-->', L2[:4])
print('Slice-->', L2[-4:])  # 后n个数
print('Slice-->', L2[:6:2])  # 前6个,2为步长
print('Slice-->', L2[::2])  # 2为步长
print('Slice-->', L2[::])  # 原样复制一个list
# tuple也是一种list，唯一区别是tuple不可变。因此，tuple也可以用切片操作，只是操作的结果仍是tuple：
print((0, 1, 2, 3, 4, 5)[:3])
# 字符串'xxx'也可以看成是一种list，每个元素就是一个字符。因此，字符串也可以用切片操作，只是操作结果仍是字符串：
print('ABCDEFG'[::2])

# 迭代
# 就是循环遍历 for...in
# Python的for循环抽象程度要高于C的for循环，因为Python的for循环不仅可以用在list或tuple上，还可以作用在其他可迭代对象上
d = {'k1': 101, 'k2': 102, 'k3': 103}
for k in d:
    print(d[k])
for v in d.values():
    print(v)
for k, v in d.items():
    print(k, '--->', v)

for ch in 'abcdef':
    print(ch)
# 如何判断一个对象是可迭代对象呢？方法是通过collections模块的Iterable类型判断：
# from collections import Iterable
# 上面的import报错:
# DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working from collections import Iterable
# 大意是这个从collections中import的方法即将被废弃，且在3.8版本中停止工作
from collections.abc import Iterable

print(isinstance('abc', Iterable))
print(isinstance(100, Iterable))
print(isinstance([1, 2, 3], Iterable))
# 如果要对list实现类似Java那样的下标循环怎么办？Python内置的enumerate函数可以把一个list变成索引-元素对，这样就可以在for循环中同时迭代索引和元素本身：
for i, value in enumerate('abcdef'):
    print(i, value)
# for循环里，同时引用了两个变量，在Python里是很常见的
for x, y in [(1, 1), (2, 4), (3, 9)]:
    print(x, y)

# 列表生成式
#  是Python内置的非常简单却强大的可以用来创建list的生成式
L3 = list(range(1, 20, 2))
print(L3)
# 但如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做？
L4 = [x * x for x in range(1, 10)]
print(L4)
# for循环后面还可以加上if判断，这样我们就可以筛选出仅偶数的平方：
L5 = [x * x for x in range(1, 10) if x % 2 == 0]
print(L5)
# 还可以使用两层循环，可以生成全排列：
L6 = [m + n for m in 'abcde' for n in 'qwer']
print(L6)
# 运用列表生成式，可以写出非常简洁的代码。例如，列出当前目录下的所有文件和目录名，可以通过一行代码实现：
import os

L7 = [d for d in os.listdir('.')]
print(L7)
# for循环可以同时使用两个甚至多个变量，比如dict的items()可以同时迭代key和value：
# 列表生成式也可以使用两个变量来生成list：
d = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
L8 = [k + '=' + v for k, v in d.items()]
print(L8)
# 把一个list中所有的字符串变成小写
L = ['Hello', 'World', 'IBM', 'Apple']
print(L)
L9 = [s.lower() for s in L]
print(L9)

# 生成器
# 通过列表生成式，我们可以直接创建一个列表。但是，受到内存限制，列表容量肯定是有限的。而且，创建一个包含100万个元素的列表，
# 不仅占用很大的存储空间，如果我们仅仅需要访问前面几个元素，那后面绝大多数元素占用的空间都白白浪费了。
# 所以，如果列表元素可以按照某种算法推算出来，那我们是否可以在循环的过程中不断推算出后续的元素呢？这样就不必创建完整的list，
# 从而节省大量的空间。在Python中，这种一边循环一边计算的机制，称为生成器：generator。
# 第一种方法很简单，只要把一个列表生成式的[]改成()，就创建了一个generator：
g = (x * x for x in range(10))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
for i in g:
    print('g-->', i)


# 如果推算的算法比较复杂，用类似列表生成式的for循环无法实现的时候，还可以用函数来实现。
# 比如，著名的斐波拉契数列（Fibonacci），除第一个和第二个数外，任意一个数都可由前两个数相加得到：
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b  # 相当于: t = (b, a + b) , t是一个tuple, a = t[0], b = t[1]
        n += 1
    print('fib done')


fib(8)


# 上面的函数和generator仅一步之遥。要把fib函数变成generator，只需要把print(b)改为yield b就可以了：
# 这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字，那么这个函数就不再是一个普通函数，而是一个generator：
def fib2(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b  # 相当于: t = (b, a + b) , t是一个tuple, a = t[0], b = t[1]
        n += 1
    print('fib2 done')
    return "fib2 done return"


f = fib2(8)
for n in f:
    print('n-->', n)


# 这里，最难理解的就是generator和函数的执行流程不一样。函数是顺序执行，遇到return语句或者最后一行函数语句就返回。而变成generator的函数，
# 在每次调用next()的时候执行，遇到yield语句返回，再次执行时从上次返回的yield语句处继续执行。
def odd():
    print('step1')
    yield 1
    print('step2')
    yield 3
    print('step3')
    yield 5


o = odd()
print('next-->', next(o))
for i in o:
    print('for--->', i)
# 但是用for循环调用generator时，发现拿不到generator的return语句的返回值。如果想要拿到返回值，必须捕获StopIteration错误，返回值包含在StopIteration的value中：
f = fib2(6)
while True:
    try:
        x = next(f)
        print('while--->', x)
    except StopIteration as e:
        print('Generator return value:', e.value)
        break

# 迭代器
# 可以被next()函数调用并不断返回下一个值的对象称为迭代器：Iterator。
# 可以使用isinstance()判断一个对象是否是Iterator对象
# (之前有用到过Iterable,这个要区分开)
from collections.abc import Iterator

b = isinstance((x for x in range(10)), Iterator)
print('b--->', b)
print(isinstance([], Iterator))
print(isinstance({}, Iterator))
print(isinstance((), Iterator))
print(isinstance('abc', Iterator))
# 生成器都是Iterator对象，但list、dict、str虽然是Iterable，却不是Iterator。
# 把list、dict、str等Iterable变成Iterator可以使用iter()函数：
print(isinstance(iter('abc'),Iterator))
# 你可能会问，为什么list、dict、str等数据类型不是Iterator？
# 这是因为Python的Iterator对象表示的是一个数据流，Iterator对象可以被next()函数调用并不断返回下一个数据，
# 直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列，但我们却不能提前知道序列的长度，
# 只能不断通过next()函数实现按需计算下一个数据，所以Iterator的计算是惰性的，只有在需要返回下一个数据时它才会计算。
# Iterator甚至可以表示一个无限大的数据流，例如全体自然数。而使用list是永远不可能存储全体自然数的。
